{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<table style=\"border: none\" align=\"center\">\n",
    "   <tr style=\"border: none\">\n",
    "      <th style=\"border: none\"><font face=\"verdana\" size=\"4\" color=\"black\"><b>  Demonstrate adversarial training using ART  </b></font></font></th>\n",
    "   </tr> \n",
    "</table>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook we demonstrate adversarial training using ART on the MNIST dataset.\n",
    "\n",
    "\n",
    "## Contents\n",
    "\n",
    "1.\t[Load prereqs and data](#prereqs)\n",
    "2.  [Train and evaluate a baseline classifier](#classifier)\n",
    "3.  [Adversarially train a robust classifier](#adv_training)\n",
    "4.\t[Evaluate the robust classifier](#evaluation)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"prereqs\"></a>\n",
    "## 1. Load prereqs and data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "from keras.models import load_model\n",
    "\n",
    "from art.config import ART_DATA_PATH\n",
    "from art.utils import load_dataset, get_file\n",
    "from art.classifiers import KerasClassifier\n",
    "from art.attacks import FastGradientMethod\n",
    "from art.attacks import BasicIterativeMethod\n",
    "from art.defences import AdversarialTrainer\n",
    "\n",
    "import numpy as np\n",
    "\n",
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "(x_train, y_train), (x_test, y_test), min_, max_ = load_dataset('mnist')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"classifier\"></a>\n",
    "## 2. Train and evaluate a baseline classifier"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Load the classifier model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "path = get_file('mnist_cnn_original.h5', extract=False, path=ART_DATA_PATH,\n",
    "                url='https://www.dropbox.com/s/p2nyzne9chcerid/mnist_cnn_original.h5?dl=1')\n",
    "classifier_model = load_model(path)\n",
    "classifier = KerasClassifier(clip_values=(min_, max_), model=classifier_model, use_logits=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "conv2d_1 (Conv2D)            (None, 26, 26, 32)        320       \n",
      "_________________________________________________________________\n",
      "max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_2 (Conv2D)            (None, 11, 11, 64)        18496     \n",
      "_________________________________________________________________\n",
      "max_pooling2d_2 (MaxPooling2 (None, 5, 5, 64)          0         \n",
      "_________________________________________________________________\n",
      "flatten_1 (Flatten)          (None, 1600)              0         \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 128)               204928    \n",
      "_________________________________________________________________\n",
      "dense_2 (Dense)              (None, 10)                1290      \n",
      "=================================================================\n",
      "Total params: 225,034\n",
      "Trainable params: 225,034\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "classifier_model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Evaluate the classifier performance on the first 100 original test samples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original test data (first 100 images):\n",
      "Correctly classified: 100\n",
      "Incorrectly classified: 0\n"
     ]
    }
   ],
   "source": [
    "x_test_pred = np.argmax(classifier.predict(x_test[:100]), axis=1)\n",
    "nb_correct_pred = np.sum(x_test_pred == np.argmax(y_test[:100], axis=1))\n",
    "\n",
    "print(\"Original test data (first 100 images):\")\n",
    "print(\"Correctly classified: {}\".format(nb_correct_pred))\n",
    "print(\"Incorrectly classified: {}\".format(100-nb_correct_pred))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Generate some adversarial samples:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "attacker = FastGradientMethod(classifier, eps=0.5)\n",
    "x_test_adv = attacker.generate(x_test[:100])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And evaluate performance on those:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Adversarial test data (first 100 images):\n",
      "Correctly classified: 22\n",
      "Incorrectly classified: 78\n"
     ]
    }
   ],
   "source": [
    "x_test_adv_pred = np.argmax(classifier.predict(x_test_adv), axis=1)\n",
    "nb_correct_adv_pred = np.sum(x_test_adv_pred == np.argmax(y_test[:100], axis=1))\n",
    "\n",
    "print(\"Adversarial test data (first 100 images):\")\n",
    "print(\"Correctly classified: {}\".format(nb_correct_adv_pred))\n",
    "print(\"Incorrectly classified: {}\".format(100-nb_correct_adv_pred))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"adv_training\"></a>\n",
    "## 3. Adversarially train a robust classifier"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "path = get_file('mnist_cnn_robust.h5', extract=False, path=ART_DATA_PATH,\n",
    "                url='https://www.dropbox.com/s/yutsncaniiy5uy8/mnist_cnn_robust.h5?dl=1')\n",
    "robust_classifier_model = load_model(path)\n",
    "robust_classifier = KerasClassifier(clip_values=(min_, max_), model=robust_classifier_model, use_logits=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: the robust classifier has the same architecture as above, except the first dense layer has **1024** instead of **128** units. (This was recommend by Madry et al. (2017), *Towards Deep Learning Models Resistant to Adversarial Attacks*)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "conv2d_3 (Conv2D)            (None, 26, 26, 32)        320       \n",
      "_________________________________________________________________\n",
      "max_pooling2d_3 (MaxPooling2 (None, 13, 13, 32)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_4 (Conv2D)            (None, 11, 11, 64)        18496     \n",
      "_________________________________________________________________\n",
      "max_pooling2d_4 (MaxPooling2 (None, 5, 5, 64)          0         \n",
      "_________________________________________________________________\n",
      "flatten_2 (Flatten)          (None, 1600)              0         \n",
      "_________________________________________________________________\n",
      "dense_3 (Dense)              (None, 1024)              1639424   \n",
      "_________________________________________________________________\n",
      "dense_4 (Dense)              (None, 10)                10250     \n",
      "=================================================================\n",
      "Total params: 1,668,490\n",
      "Trainable params: 1,668,490\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "robust_classifier_model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Also as recommended by Madry et al., we use BIM/PGD attacks during adversarial training:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "attacks = BasicIterativeMethod(robust_classifier, eps=0.3, eps_step=0.01, max_iter=40)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Perform adversarial training:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "# We had performed this before, starting with a randomly intialized model.\n",
    "# Adversarial training takes about 80 minutes on an NVIDIA V100.\n",
    "# The resulting model is the one loaded from mnist_cnn_robust.h5 above.\n",
    "\n",
    "# Here is the command we had used for the Adversarial Training\n",
    "\n",
    "# trainer = AdversarialTrainer(robust_classifier, attacks, ratio=1.0)\n",
    "# trainer.fit(x_train, y_train, nb_epochs=83, batch_size=50)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"evaluation\"></a>\n",
    "## 4. Evaluate the robust classifier"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Evaluate the robust classifier's performance on the original test data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original test data (first 100 images):\n",
      "Correctly classified: 99\n",
      "Incorrectly classified: 1\n"
     ]
    }
   ],
   "source": [
    "x_test_robust_pred = np.argmax(robust_classifier.predict(x_test[:100]), axis=1)\n",
    "nb_correct_robust_pred = np.sum(x_test_robust_pred == np.argmax(y_test[:100], axis=1))\n",
    "\n",
    "print(\"Original test data (first 100 images):\")\n",
    "print(\"Correctly classified: {}\".format(nb_correct_robust_pred))\n",
    "print(\"Incorrectly classified: {}\".format(100-nb_correct_robust_pred))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Evaluate the robust classifier's performance on the adversarial test data (**white-box** setting):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "attacker_robust = FastGradientMethod(robust_classifier, eps=0.5)\n",
    "x_test_adv_robust = attacker_robust.generate(x_test[:100])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Adversarial test data (first 100 images):\n",
      "Correctly classified: 79\n",
      "Incorrectly classified: 21\n"
     ]
    }
   ],
   "source": [
    "x_test_adv_robust_pred = np.argmax(robust_classifier.predict(x_test_adv_robust), axis=1)\n",
    "nb_correct_adv_robust_pred = np.sum(x_test_adv_robust_pred == np.argmax(y_test[:100], axis=1))\n",
    "\n",
    "print(\"Adversarial test data (first 100 images):\")\n",
    "print(\"Correctly classified: {}\".format(nb_correct_adv_robust_pred))\n",
    "print(\"Incorrectly classified: {}\".format(100-nb_correct_adv_robust_pred))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Compare the performance of the original and the robust classifier over a range of `eps` values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "eps_range = [0.01, 0.02, 0.03, 0.04, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n",
    "nb_correct_original = []\n",
    "nb_correct_robust = []\n",
    "\n",
    "for eps in eps_range:\n",
    "    attacker.set_params(**{'eps': eps})\n",
    "    attacker_robust.set_params(**{'eps': eps})\n",
    "    x_test_adv = attacker.generate(x_test[:100])\n",
    "    x_test_adv_robust = attacker_robust.generate(x_test[:100])\n",
    "    \n",
    "    x_test_adv_pred = np.argmax(classifier.predict(x_test_adv), axis=1)\n",
    "    nb_correct_original += [np.sum(x_test_adv_pred == np.argmax(y_test[:100], axis=1))]\n",
    "    \n",
    "    x_test_adv_robust_pred = np.argmax(robust_classifier.predict(x_test_adv_robust), axis=1)\n",
    "    nb_correct_robust += [np.sum(x_test_adv_robust_pred == np.argmax(y_test[:100], axis=1))]\n",
    "\n",
    "eps_range = [0] + eps_range\n",
    "nb_correct_original = [nb_correct_pred] + nb_correct_original\n",
    "nb_correct_robust = [nb_correct_robust_pred] + nb_correct_robust"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots()\n",
    "ax.plot(np.array(eps_range), np.array(nb_correct_original), 'b--', label='Original classifier')\n",
    "ax.plot(np.array(eps_range), np.array(nb_correct_robust), 'r--', label='Robust classifier')\n",
    "\n",
    "legend = ax.legend(loc='upper center', shadow=True, fontsize='large')\n",
    "legend.get_frame().set_facecolor('#00FFCC')\n",
    "\n",
    "plt.xlabel('Attack strength (eps)')\n",
    "plt.ylabel('Correct predictions')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv36",
   "language": "python",
   "name": "venv36"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
